﻿/**
* 
* Find more about the scrolling function at
* http://cubiq.org/iscroll
*
* Copyright (c) 2010 Matteo Spinelli, http://cubiq.org/
* Released under MIT license
* http://cubiq.org/dropbox/mit-license.txt
* 
* Version 3.7.1 - Last updated: 2010.10.08
* 
*/

(function () {
    function iScroll(el, options) {
        var that = this, i;
        that.element = typeof el == 'object' ? el : document.getElementById(el);
        that.wrapper = that.element.parentNode;

        that.element.style.webkitTransitionProperty = '-webkit-transform';
        that.element.style.webkitTransitionTimingFunction = 'cubic-bezier(0,0,0.25,1)';
        that.element.style.webkitTransitionDuration = '0';
        that.element.style.webkitTransform = translateOpen + '0,0' + translateClose;

        // Default options
        that.options = {
            bounce: has3d,
            momentum: has3d,
            checkDOMChanges: true,
            topOnDOMChanges: false,
            hScrollbar: has3d,
            vScrollbar: has3d,
            fadeScrollbar: isIthing || !isTouch,
            shrinkScrollbar: isIthing || !isTouch,
            desktopCompatibility: false,
            overflow: 'auto',
            snap: false,
            bounceLock: false,
            scrollbarColor: 'rgba(0,0,0,0.5)',
            onScrollEnd: function () { }
        };

        // User defined options
        if (typeof options == 'object') {
            for (i in options) {
                that.options[i] = options[i];
            }
        }

        if (that.options.desktopCompatibility) {
            that.options.overflow = 'hidden';
        }

        that.onScrollEnd = that.options.onScrollEnd;
        delete that.options.onScrollEnd;

        that.wrapper.style.overflow = that.options.overflow;

        that.refresh();

        window.addEventListener('onorientationchange' in window ? 'orientationchange' : 'resize', that, false);

        if (isTouch || that.options.desktopCompatibility) {
            that.element.addEventListener(START_EVENT, that, false);
            that.element.addEventListener(MOVE_EVENT, that, false);
            that.element.addEventListener(END_EVENT, that, false);
        }

        if (that.options.checkDOMChanges) {
            that.element.addEventListener('DOMSubtreeModified', that, false);
        }
    }

    iScroll.prototype = {
        x: 0,
        y: 0,
        enabled: true,

        handleEvent: function (e) {
            var that = this;

            switch (e.type) {
                case START_EVENT:
                    that.touchStart(e);
                    break;
                case MOVE_EVENT:
                    that.touchMove(e);
                    break;
                case END_EVENT:
                    that.touchEnd(e);
                    break;
                case 'webkitTransitionEnd':
                    that.transitionEnd();
                    break;
                case 'orientationchange':
                case 'resize':
                    that.refresh();
                    break;
                case 'DOMSubtreeModified':
                    that.onDOMModified(e);
                    break;
            }
        },

        onDOMModified: function (e) {
            var that = this;

            // (Hopefully) execute onDOMModified only once
            if (e.target.parentNode != that.element) {
                return;
            }

            setTimeout(function () { that.refresh(); }, 0);

            if (that.options.topOnDOMChanges && (that.x != 0 || that.y != 0)) {
                that.scrollTo(0, 0, '0');
            }
        },

        refresh: function () {
            var that = this,
			resetX = that.x, resetY = that.y,
			snap;

            that.scrollWidth = that.wrapper.clientWidth;
            that.scrollHeight = that.wrapper.clientHeight;
            that.scrollerWidth = that.element.offsetWidth;
            that.scrollerHeight = that.element.offsetHeight;
            that.maxScrollX = that.scrollWidth - that.scrollerWidth;
            that.maxScrollY = that.scrollHeight - that.scrollerHeight;
            that.directionX = 0;
            that.directionY = 0;

            if (that.scrollX) {
                if (that.maxScrollX >= 0) {
                    resetX = 0;
                } else if (that.x < that.maxScrollX) {
                    resetX = that.maxScrollX;
                }
            }
            if (that.scrollY) {
                if (that.maxScrollY >= 0) {
                    resetY = 0;
                } else if (that.y < that.maxScrollY) {
                    resetY = that.maxScrollY;
                }
            }

            // Snap
            if (that.options.snap) {
                that.maxPageX = -Math.floor(that.maxScrollX / that.scrollWidth);
                that.maxPageY = -Math.floor(that.maxScrollY / that.scrollHeight);

                snap = that.snap(resetX, resetY);
                resetX = snap.x;
                resetY = snap.y;
            }

            if (resetX != that.x || resetY != that.y) {
                that.setTransitionTime('0');
                that.setPosition(resetX, resetY, true);
            }

            that.scrollX = that.scrollerWidth > that.scrollWidth;
            that.scrollY = !that.options.bounceLock && !that.scrollX || that.scrollerHeight > that.scrollHeight;

            // Update horizontal scrollbar
            if (that.options.hScrollbar && that.scrollX) {
                that.scrollBarX = that.scrollBarX || new scrollbar('horizontal', that.wrapper, that.options.fadeScrollbar, that.options.shrinkScrollbar, that.options.scrollbarColor);
                that.scrollBarX.init(that.scrollWidth, that.scrollerWidth);
            } else if (that.scrollBarX) {
                that.scrollBarX = that.scrollBarX.remove();
            }

            // Update vertical scrollbar
            if (that.options.vScrollbar && that.scrollY && that.scrollerHeight > that.scrollHeight) {
                that.scrollBarY = that.scrollBarY || new scrollbar('vertical', that.wrapper, that.options.fadeScrollbar, that.options.shrinkScrollbar, that.options.scrollbarColor);
                that.scrollBarY.init(that.scrollHeight, that.scrollerHeight);
            } else if (that.scrollBarY) {
                that.scrollBarY = that.scrollBarY.remove();
            }
        },

        setPosition: function (x, y, hideScrollBars) {
            var that = this;

            that.x = x;
            that.y = y;

            that.element.style.webkitTransform = translateOpen + that.x + 'px,' + that.y + 'px' + translateClose;

            // Move the scrollbars
            if (!hideScrollBars) {
                if (that.scrollBarX) {
                    that.scrollBarX.setPosition(that.x);
                }
                if (that.scrollBarY) {
                    that.scrollBarY.setPosition(that.y);
                }
            }
        },

        setTransitionTime: function (time) {
            var that = this;

            time = time || '0';
            that.element.style.webkitTransitionDuration = time;

            if (that.scrollBarX) {
                that.scrollBarX.bar.style.webkitTransitionDuration = time;
                that.scrollBarX.wrapper.style.webkitTransitionDuration = has3d && that.options.fadeScrollbar ? '300ms' : '0';
            }
            if (that.scrollBarY) {
                that.scrollBarY.bar.style.webkitTransitionDuration = time;
                that.scrollBarY.wrapper.style.webkitTransitionDuration = has3d && that.options.fadeScrollbar ? '300ms' : '0';
            }
        },

        touchStart: function (e) {
            var that = this,
			matrix;

            if (!that.enabled) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            that.scrolling = true; 	// This is probably not needed, but may be useful if iScroll is used in conjuction with other frameworks

            that.moved = false;
            that.distX = 0;
            that.distY = 0;

            that.setTransitionTime('0');

            // Check if the scroller is really where it should be
            if (that.options.momentum || that.options.snap) {
                matrix = new WebKitCSSMatrix(window.getComputedStyle(that.element).webkitTransform);
                if (matrix.e != that.x || matrix.f != that.y) {
                    document.removeEventListener('webkitTransitionEnd', that, false);
                    that.setPosition(matrix.e, matrix.f);
                    that.moved = true;
                }
            }

            that.touchStartX = isTouch ? e.changedTouches[0].pageX : e.pageX;
            that.scrollStartX = that.x;

            that.touchStartY = isTouch ? e.changedTouches[0].pageY : e.pageY;
            that.scrollStartY = that.y;

            that.scrollStartTime = e.timeStamp;

            that.directionX = 0;
            that.directionY = 0;
        },

        touchMove: function (e) {
            if (!this.scrolling) {
                return;
            }

            var that = this,
			pageX = isTouch ? e.changedTouches[0].pageX : e.pageX,
			pageY = isTouch ? e.changedTouches[0].pageY : e.pageY,
			leftDelta = that.scrollX ? pageX - that.touchStartX : 0,
			topDelta = that.scrollY ? pageY - that.touchStartY : 0,
			newX = that.x + leftDelta,
			newY = that.y + topDelta;

            //e.preventDefault();
            e.stopPropagation(); // Stopping propagation just saves some cpu cycles (I presume)

            that.touchStartX = pageX;
            that.touchStartY = pageY;

            // Slow down if outside of the boundaries
            if (newX >= 0 || newX < that.maxScrollX) {
                newX = that.options.bounce ? Math.round(that.x + leftDelta / 3) : (newX >= 0 || that.maxScrollX >= 0) ? 0 : that.maxScrollX;
            }
            if (newY >= 0 || newY < that.maxScrollY) {
                newY = that.options.bounce ? Math.round(that.y + topDelta / 3) : (newY >= 0 || that.maxScrollY >= 0) ? 0 : that.maxScrollY;
            }

            if (that.distX + that.distY > 5) {			// 5 pixels threshold

                // Lock scroll direction
                if (that.distX - 3 > that.distY) {
                    newY = that.y;
                    topDelta = 0;
                } else if (that.distY - 3 > that.distX) {
                    newX = that.x;
                    leftDelta = 0;
                }

                that.setPosition(newX, newY);
                that.moved = true;
                that.directionX = leftDelta > 0 ? -1 : 1;
                that.directionY = topDelta > 0 ? -1 : 1;
            } else {
                that.distX += Math.abs(leftDelta);
                that.distY += Math.abs(topDelta);
                //			that.dist+= Math.abs(leftDelta) + Math.abs(topDelta);
            }
        },

        touchEnd: function (e) {
            if (!this.scrolling) {
                return;
            }

            var that = this,
			time = e.timeStamp - that.scrollStartTime,
			point = isTouch ? e.changedTouches[0] : e,
			target, ev,
			momentumX, momentumY,
			newDuration = 0,
			newPositionX = that.x, newPositionY = that.y,
			snap;

            that.scrolling = false;

            if (!that.moved) {
                that.resetPosition();

                if (isTouch) {
                    // Find the last touched element
                    target = point.target;
                    while (target.nodeType != 1) {
                        target = target.parentNode;
                    }

                    // Create the fake event
                    ev = document.createEvent('MouseEvents');
                    ev.initMouseEvent('click', true, true, e.view, 1,
					point.screenX, point.screenY, point.clientX, point.clientY,
					e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
					0, null);
                    ev._fake = true;
                    target.dispatchEvent(ev);
                }

                return;
            }

            if (!that.options.snap && time > 250) {			// Prevent slingshot effect
                that.resetPosition();
                return;
            }

            if (that.options.momentum) {
                momentumX = that.scrollX === true
				? that.momentum(that.x - that.scrollStartX,
								time,
								that.options.bounce ? -that.x + that.scrollWidth / 5 : -that.x,
								that.options.bounce ? that.x + that.scrollerWidth - that.scrollWidth + that.scrollWidth / 5 : that.x + that.scrollerWidth - that.scrollWidth)
				: { dist: 0, time: 0 };

                momentumY = that.scrollY === true
				? that.momentum(that.y - that.scrollStartY,
								time,
								that.options.bounce ? -that.y + that.scrollHeight / 5 : -that.y,
								that.options.bounce ? (that.maxScrollY < 0 ? that.y + that.scrollerHeight - that.scrollHeight : 0) + that.scrollHeight / 5 : that.y + that.scrollerHeight - that.scrollHeight)
				: { dist: 0, time: 0 };

                newDuration = Math.max(Math.max(momentumX.time, momentumY.time), 1); 	// The minimum animation length must be 1ms
                newPositionX = that.x + momentumX.dist;
                newPositionY = that.y + momentumY.dist;
            }

            if (that.options.snap) {
                snap = that.snap(newPositionX, newPositionY);
                newPositionX = snap.x;
                newPositionY = snap.y;
                newDuration = Math.max(snap.time, newDuration);
            }

            that.scrollTo(newPositionX, newPositionY, newDuration + 'ms');
        },

        transitionEnd: function () {
            var that = this;
            document.removeEventListener('webkitTransitionEnd', that, false);
            that.resetPosition();
        },

        resetPosition: function () {
            var that = this,
			resetX = that.x,
		 	resetY = that.y;

            if (that.x >= 0) {
                resetX = 0;
            } else if (that.x < that.maxScrollX) {
                resetX = that.maxScrollX;
            }

            if (that.y >= 0 || that.maxScrollY > 0) {
                resetY = 0;
            } else if (that.y < that.maxScrollY) {
                resetY = that.maxScrollY;
            }

            if (resetX != that.x || resetY != that.y) {
                that.scrollTo(resetX, resetY);
            } else {
                if (that.moved) {
                    that.onScrollEnd(); 	// Execute custom code on scroll end
                    that.moved = false;
                }

                // Hide the scrollbars
                if (that.scrollBarX) {
                    that.scrollBarX.hide();
                }
                if (that.scrollBarY) {
                    that.scrollBarY.hide();
                }
            }
        },

        snap: function (x, y) {
            var that = this, time;

            if (that.directionX > 0) {
                x = Math.floor(x / that.scrollWidth);
            } else if (that.directionX < 0) {
                x = Math.ceil(x / that.scrollWidth);
            } else {
                x = Math.round(x / that.scrollWidth);
            }
            that.pageX = -x;
            x = x * that.scrollWidth;
            if (x > 0) {
                x = that.pageX = 0;
            } else if (x < that.maxScrollX) {
                that.pageX = that.maxPageX;
                x = that.maxScrollX;
            }

            if (that.directionY > 0) {
                y = Math.floor(y / that.scrollHeight);
            } else if (that.directionY < 0) {
                y = Math.ceil(y / that.scrollHeight);
            } else {
                y = Math.round(y / that.scrollHeight);
            }
            that.pageY = -y;
            y = y * that.scrollHeight;
            if (y > 0) {
                y = that.pageY = 0;
            } else if (y < that.maxScrollY) {
                that.pageY = that.maxPageY;
                y = that.maxScrollY;
            }

            // Snap with constant speed (proportional duration)
            time = Math.round(Math.max(
				Math.abs(that.x - x) / that.scrollWidth * 500,
				Math.abs(that.y - y) / that.scrollHeight * 500
			));

            return { x: x, y: y, time: time };
        },

        scrollTo: function (destX, destY, runtime) {
            var that = this;

            if (that.x == destX && that.y == destY) {
                that.resetPosition();
                return;
            }

            that.moved = true;
            that.setTransitionTime(runtime || '350ms');
            that.setPosition(destX, destY);

            if (runtime === '0' || runtime == '0s' || runtime == '0ms') {
                that.resetPosition();
            } else {
                document.addEventListener('webkitTransitionEnd', that, false); // At the end of the transition check if we are still inside of the boundaries
            }
        },

        scrollToPage: function (pageX, pageY, runtime) {
            var that = this, snap;

            if (!that.options.snap) {
                that.pageX = -Math.round(that.x / that.scrollWidth);
                that.pageY = -Math.round(that.y / that.scrollHeight);
            }

            if (pageX == 'next') {
                pageX = ++that.pageX;
            } else if (pageX == 'prev') {
                pageX = --that.pageX;
            }

            if (pageY == 'next') {
                pageY = ++that.pageY;
            } else if (pageY == 'prev') {
                pageY = --that.pageY;
            }

            pageX = -pageX * that.scrollWidth;
            pageY = -pageY * that.scrollHeight;

            snap = that.snap(pageX, pageY);
            pageX = snap.x;
            pageY = snap.y;

            that.scrollTo(pageX, pageY, runtime || '500ms');
        },

        scrollToElement: function (el, runtime) {
            el = typeof el == 'object' ? el : this.element.querySelector(el);

            if (!el) {
                return;
            }

            var that = this,
			x = that.scrollX ? -el.offsetLeft : 0,
			y = that.scrollY ? -el.offsetTop : 0;

            if (x >= 0) {
                x = 0;
            } else if (x < that.maxScrollX) {
                x = that.maxScrollX;
            }

            if (y >= 0) {
                y = 0;
            } else if (y < that.maxScrollY) {
                y = that.maxScrollY;
            }

            that.scrollTo(x, y, runtime);
        },

        momentum: function (dist, time, maxDistUpper, maxDistLower) {
            var friction = 2.5,
			deceleration = 1.2,
			speed = Math.abs(dist) / time * 1000,
			newDist = speed * speed / friction / 1000,
			newTime = 0;

            // Proportinally reduce speed if we are outside of the boundaries 
            if (dist > 0 && newDist > maxDistUpper) {
                speed = speed * maxDistUpper / newDist / friction;
                newDist = maxDistUpper;
            } else if (dist < 0 && newDist > maxDistLower) {
                speed = speed * maxDistLower / newDist / friction;
                newDist = maxDistLower;
            }

            newDist = newDist * (dist < 0 ? -1 : 1);
            newTime = speed / deceleration;

            return { dist: Math.round(newDist), time: Math.round(newTime) };
        },

        destroy: function (full) {
            var that = this;

            window.removeEventListener('onorientationchange' in window ? 'orientationchange' : 'resize', that, false);
            that.element.removeEventListener(START_EVENT, that, false);
            that.element.removeEventListener(MOVE_EVENT, that, false);
            that.element.removeEventListener(END_EVENT, that, false);
            document.removeEventListener('webkitTransitionEnd', that, false);

            if (that.options.checkDOMChanges) {
                that.element.removeEventListener('DOMSubtreeModified', that, false);
            }

            if (that.scrollBarX) {
                that.scrollBarX = that.scrollBarX.remove();
            }

            if (that.scrollBarY) {
                that.scrollBarY = that.scrollBarY.remove();
            }

            if (full) {
                that.wrapper.parentNode.removeChild(that.wrapper);
            }

            return null;
        }
    };

    function scrollbar(dir, wrapper, fade, shrink, color) {
        var that = this,
		doc = document;

        that.dir = dir;
        that.fade = fade;
        that.shrink = shrink;
        that.uid = ++uid;

        // Create main scrollbar
        that.bar = doc.createElement('div');

        that.bar.style.cssText = 'position:absolute;top:0;left:0;-webkit-transition-timing-function:cubic-bezier(0,0,0.25,1);pointer-events:none;-webkit-transition-duration:0;-webkit-transition-delay:0;-webkit-transition-property:-webkit-transform;z-index:10;background:' + color + ';' +
		'-webkit-transform:' + translateOpen + '0,0' + translateClose + ';' +
		(dir == 'horizontal' ? '-webkit-border-radius:3px 2px;min-width:6px;min-height:5px' : '-webkit-border-radius:2px 3px;min-width:5px;min-height:6px');

        // Create scrollbar wrapper
        that.wrapper = doc.createElement('div');
        that.wrapper.style.cssText = '-webkit-mask:-webkit-canvas(scrollbar' + that.uid + that.dir + ');position:absolute;z-index:10;pointer-events:none;overflow:hidden;opacity:0;-webkit-transition-duration:' + (fade ? '300ms' : '0') + ';-webkit-transition-delay:0;-webkit-transition-property:opacity;' +
		(that.dir == 'horizontal' ? 'bottom:2px;left:2px;right:7px;height:5px' : 'top:2px;right:2px;bottom:7px;width:5px;');

        // Add scrollbar to the DOM
        that.wrapper.appendChild(that.bar);
        wrapper.appendChild(that.wrapper);
    }

    scrollbar.prototype = {
        init: function (scroll, size) {
            var that = this,
			doc = document,
			pi = Math.PI,
			ctx;

            // Create scrollbar mask
            if (that.dir == 'horizontal') {
                if (that.maxSize != that.wrapper.offsetWidth) {
                    that.maxSize = that.wrapper.offsetWidth;
                    ctx = doc.getCSSCanvasContext("2d", "scrollbar" + that.uid + that.dir, that.maxSize, 5);
                    ctx.fillStyle = "rgb(0,0,0)";
                    ctx.beginPath();
                    ctx.arc(2.5, 2.5, 2.5, pi / 2, -pi / 2, false);
                    ctx.lineTo(that.maxSize - 2.5, 0);
                    ctx.arc(that.maxSize - 2.5, 2.5, 2.5, -pi / 2, pi / 2, false);
                    ctx.closePath();
                    ctx.fill();
                }
            } else {
                if (that.maxSize != that.wrapper.offsetHeight) {
                    that.maxSize = that.wrapper.offsetHeight;
                    ctx = doc.getCSSCanvasContext("2d", "scrollbar" + that.uid + that.dir, 5, that.maxSize);
                    ctx.fillStyle = "rgb(0,0,0)";
                    ctx.beginPath();
                    ctx.arc(2.5, 2.5, 2.5, pi, 0, false);
                    ctx.lineTo(5, that.maxSize - 2.5);
                    ctx.arc(2.5, that.maxSize - 2.5, 2.5, 0, pi, false);
                    ctx.closePath();
                    ctx.fill();
                }
            }

            that.size = Math.max(Math.round(that.maxSize * that.maxSize / size), 6);
            that.maxScroll = that.maxSize - that.size;
            that.toWrapperProp = that.maxScroll / (scroll - size);
            that.bar.style[that.dir == 'horizontal' ? 'width' : 'height'] = that.size + 'px';
        },

        setPosition: function (pos) {
            var that = this;

            if (that.wrapper.style.opacity != '1') {
                that.show();
            }

            pos = Math.round(that.toWrapperProp * pos);

            if (pos < 0) {
                pos = that.shrink ? pos + pos * 3 : 0;
                if (that.size + pos < 7) {
                    pos = -that.size + 6;
                }
            } else if (pos > that.maxScroll) {
                pos = that.shrink ? pos + (pos - that.maxScroll) * 3 : that.maxScroll;
                if (that.size + that.maxScroll - pos < 7) {
                    pos = that.size + that.maxScroll - 6;
                }
            }

            pos = that.dir == 'horizontal'
			? translateOpen + pos + 'px,0' + translateClose
			: translateOpen + '0,' + pos + 'px' + translateClose;

            that.bar.style.webkitTransform = pos;
        },

        show: function () {
            if (has3d) {
                this.wrapper.style.webkitTransitionDelay = '0';
            }
            this.wrapper.style.opacity = '1';
        },

        hide: function () {
            if (has3d) {
                this.wrapper.style.webkitTransitionDelay = '350ms';
            }
            this.wrapper.style.opacity = '0';
        },

        remove: function () {
            this.wrapper.parentNode.removeChild(this.wrapper);
            return null;
        }
    };

    // Is translate3d compatible?
    var has3d = ('WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix()),
    // Device sniffing
	isIthing = (/iphone|ipad/gi).test(navigator.appVersion),
	isTouch = ('ontouchstart' in window),
    // Event sniffing
	START_EVENT = isTouch ? 'touchstart' : 'mousedown',
	MOVE_EVENT = isTouch ? 'touchmove' : 'mousemove',
	END_EVENT = isTouch ? 'touchend' : 'mouseup',
    // Translate3d helper
	translateOpen = 'translate' + (has3d ? '3d(' : '('),
	translateClose = has3d ? ',0)' : ')',
    // Unique ID
	uid = 0;

    // Expose iScroll to the world
    window.iScroll = iScroll;
})();